package fuse.LaCollaFS.Metadades;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;

import java.io.IOException;

import java.io.PrintWriter;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

//import org.apache.commons.logging.Log;
//import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

import fuse.FuseException;
import fuse.FuseStatfs;
import fuse.LaCollaFS.LaCollaFS;
import fuse.LaCollaFS.Api.*;
import fuse.LaCollaFS.Metadades.Directori;
import fuse.LaCollaFS.Metadades.Node;
import fuse.FuseStat;

import LaColla.Api.Api;
import LaColla.core.data.TimestampSummary;
import LaColla.core.data.app.ObjectLaCOLLA;
import LaColla.core.util.Debug;
import LaColla.core.util.constant;

//import com.sun.org.apache.xml.internal.resolver.helpers.Debug;

public class MetaDades {

	// fuse variables

	private FuseStatfs statfs;

	private Directori tree;

	private String defpath;

	// private String defpathmetadades = "/tmp/lacollafsmetadades";

	private static final int blockSize = 512;

	private Api api;

	private static String groupId = "hola";

	private ApiServer aserv;

	private String apiid;

	// vectors per guardar els fitxers oberts

	private Hashtable<String, FileInputStream> TableInput = new Hashtable();

	private Hashtable<String, FileOutputStream> TableOutput = new Hashtable();

	private static final Logger log = Logger.getLogger("LaCollaFS.class");

	/**
	 * Constructor de les metadades
	 * 
	 * 
	 * @param defaultpath
	 *            path per defecte on guardar el docs en local
	 * @throws RemoteException
	 */
	public MetaDades(String path, Api a, ApiServer as, String idApi)
			throws RemoteException {

		FileHandler fh;
		try {
			fh = new FileHandler("/tmp/LaCollaFS.log");
			fh.setFormatter(new SimpleFormatter());
			log.addHandler(fh);
			log.setLevel(Level.ALL);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		tree = new Directori("/");
		defpath = path;
		int files = 0;
		int dirs = 0;
		int blocks = 0;
		api = a;
		aserv = as;
		apiid = idApi;

		// inicialitza el statfs
		statfs = new FuseStatfs();
		statfs.blocks = blocks;
		statfs.blockSize = blockSize;
		statfs.blocksFree = 0;
		statfs.files = files + dirs;
		statfs.filesFree = 0;
		statfs.namelen = 2048;

		// File file = new File(defpathmetadades);
		// file.mkdirs();
		// p("dir creat!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");

		this.DesaTree();

	}

	/**
	 * Imprimeix per pantalla
	 * 
	 * @param o
	 */
	/*
	 * private static void p(Object o) { System.out.println("Metadades: " + o); }
	 */

	/**
	 * Obte el FuseStat del Objecte path
	 * 
	 * @param path
	 * @return
	 * @throws FuseException
	 */
	public FuseStat ObtenirStat(String path) throws FuseException {

		Node node;
		FuseStat stat = null;
		// p("ObtenirStat, arbre que tinc: " + tree.CrearXML());
		node = ObtenirNode(path);
		if (node == null) {
			throw new FuseException("No existeix el path")
					.initErrno(FuseException.ENOENT);
		} else
			stat = node.getStat();
		return stat;
	}

	/**
	 * Canvia el FuseStat d'un objecte i guarda el arbre de les metadades a
	 * lacolla
	 * 
	 * @param path
	 *            node a canviar l'estat
	 * @param estat
	 *            FuseStat amb els canvis a realitzar
	 */
	public void CanviarStat(String path, FuseStat estat) {

		Node node;
		node = ObtenirNode(path);
		node.setStat(estat);
		DesaTree();

	}

	/**
	 * Obté l'Objecte amb el node demanat.
	 * 
	 * @param path
	 *            path del objecte a recuperar
	 * @return retorna el Objecte en questió
	 */
	public Node ObtenirNode(String path) {
		Node node;

		this.ObtenirTree();
		node = tree.BuscarFill(path);

		return node;
	}

	public void ObrirFitxer(String path, boolean lectura, boolean escriptura) {
		Fitxer fit;
		// int fh = 0;
		fit = (Fitxer) this.ObtenirNode(path);
		File fichero = new File(defpath + path);
		try {
			fichero.createNewFile();
		} catch (IOException e1) {
			log.info("Error!!! al crear el fitxer" + e1);
		}

		// p("fitxer path a crear..." + fichero.getAbsolutePath() + ",length: "
		// + fichero.length() + ", fichero nom: " + fichero.getName());
		// p("new date " + new Date());
		String groupId = "hola";
		ObjectLaCOLLA obj;
		log.info("get id objecte = " + fit.getIdObjecte());
		if (fit.getIdObjecte().equals("null")) {
			log
					.info("el fitxer no existeix encara...escriptura obrir fitxer nou");
		} else {

			try {
				log.info("Obtenir Fitxer de La Colla");
				obj = new ObjectLaCOLLA(fit.getIdObjecte(), new Date(), "",
						groupId, fichero.length(), fichero.getAbsolutePath(),
						fichero.getName());
				log.info("ObjectLaColla creat");
				obj.setObjectId(fit.getIdObjecte());
				log.info("IdObject dipositat" + fichero.getAbsolutePath());
				log.severe("Id del Objecte a recuperar !!!!"
						+ obj.getObjectId());
				api.getObject(obj, fichero.getAbsolutePath());
				log.info("GetObject Completat!");
			} catch (Exception e) {
				log.warning("fitxer no trobat a la colla!!!! " + e);
				e.printStackTrace();
			}
		}
		log.info("Fitxer recuperat de LaColla");
		if (lectura) { // lectura!!!
			try {
				FileInputStream fis = new FileInputStream(fichero);
				TableInput.put(path, fis);

				// p("obert per lectura, fh = " + fh);
			} catch (FileNotFoundException e) {
				// p("fitxer no trobat a la cache!!!!!!!!!");
				e.printStackTrace();
			}

		}
		if (escriptura) { // escriptura
			// p("arribo aqui??????????????????");

			try {
				FileOutputStream fos = new FileOutputStream(fichero);
				TableOutput.put(path, fos);
				// fh = TableOutput.indexOf(fos);
				// TableOutput.put(path, fos);
				// p("obert per escriptura, fh = " + fh);
			} catch (FileNotFoundException e) {
				log.warning("fitxer no trobat a la cache!!!!!!!!!");
				e.printStackTrace();
			}
		}

		// return fh;

	}

	public void Llegirfitxer(String path, long offset, ByteBuffer buf) {

		FileInputStream fitxer;
		fitxer = TableInput.get(path);

		try {

			int tamany = buf.capacity();
			byte[] buffer = new byte[tamany];

			int limit = fitxer.read(buffer);
			// p("que retorna el read?? = " + limit);
			for (int i = 0; i < limit; i++) { // començant per
				buf.put(buffer[i]);
				// p(buffer[i]);
			}

		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// p(buf);
		// return buf;
	}

	/**
	 * Escriu canvis en un fitxer i el guarda a lacolla Moficia l'estat del
	 * Fitxer en l'estructura a les metadades.
	 * 
	 * TODO REPASAR OFFSET!!!! ara mateix no fa el que hauria de fer!!!!
	 * 
	 * @param path
	 *            path del fitxer a modifcar
	 * @param buf
	 *            dades a afegir al fitxer
	 * @param offset
	 *            offset on començar a escriure en el fitxer
	 */
	public void Escriurefitxer(String path, ByteBuffer buf, long offset) {
		// buf.position((int) offset);
		FileOutputStream fos;
		fos = TableOutput.get(path);
		try {

			for (long i = 0; i < buf.capacity(); i++) {
				if (i < offset) {
					// no se com moure el punter!!!!
				} else {
					fos.write(buf.get((int) i));
				}

				Node fit = this.ObtenirNode(path);
				FuseStat stat = fit.getStat();
				long v = (offset + buf.capacity());
				stat.size = max(v, stat.size);
				fit.setStat(stat);
				// p("tamany fitxer: " + stat.size);

			}

		} catch (FileNotFoundException e) {
			// p("error al no trobar un fitxer");
			// e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private long max(long a, long b) {
		if (a > b)
			return a;
		else
			return b;
	}

	public void TancarFitxer(String path) {
		FileInputStream fis;
		FileOutputStream fos;

		if (TableInput.contains(path)) {
			fis = TableInput.get(path);
			try {
				fis.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			TableInput.remove(path);
		}
		if (TableOutput.contains(path)) {
			fos = TableOutput.get(path);
			try {
				fos.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			TableOutput.remove(path);
		}

	}

	public void PujaFitxer(String path) {

		// pujar fitxer a la colla
		log.info("Pujant fitxer a laColla....");
		File fichero = new File(defpath + path);
		Fitxer fit;
		fit = (Fitxer) this.ObtenirNode(path);
		log.info("Pujant fitxer a laColla...." + fichero.getAbsolutePath()
				+ fichero.length() + fichero.getName());
		ObjectLaCOLLA obj = new ObjectLaCOLLA("fitxer qualsevol", groupId,
				fichero.length(), fichero.getAbsolutePath(), fichero.getName());

		log.info("ObjecteLacolla : " + obj);
		try {
			obj = api.putObject(obj);
		} catch (Exception e) {
			log.warning("ERROR!!!!!");
			e.printStackTrace();
		}
		log.info("Fitxer desat a la colla");
		if (fit.getIdObjecte() == "null") {

			fit.setIdObjecte(obj.getObjectId());
			FuseStat estat = fit.getStat();
			estat.size = fichero.length();
			estat.blocks = (int) ((estat.size + 511L) / 512L);
			fit.setStat(estat);
			fit.setVersions(1);
			log.severe("Id del Objecte dipositat!!!!" + fit.getIdObjecte());
		} else {
			Fitxer nou = new Fitxer(fit.getNom());
			nou = fit.copy();

			nou.setIdObjecte(obj.getObjectId());

			FuseStat estat = nou.getStat();
			estat.size = fichero.length();
			estat.blocks = (int) ((estat.size + 511L) / 512L);
			nou.setStat(estat);

			Directori pare = (Directori) this.ObtenirNode(ObtenirDir(path));

			pare.EliminarFill(fit.getNom());
			nou.AfegirAnterior(fit);
			pare.AfegirFill(nou);
			log.severe("Id del Objecte dipositat!!!!" + nou.getIdObjecte());
		}
		log.info("arribo aqui????");
		DesaTree();

	}

	/**
	 * Obté l'arbre de les metadades del fitxer XML guardat a LaColla
	 * 
	 * @throws FileNotFoundException
	 * @throws JDOMException
	 * @throws IOException
	 */
	public void ObtenirTree() {
		Element root;
		Document doc;
		String xml;
		SAXBuilder builder;

		xml = ((ApiImpl) aserv.getASAI()).getTree();

		builder = new SAXBuilder();
		try {

			doc = builder.build(new StringReader(xml));
			root = doc.getRootElement();
			tree.LlegirXML(root);

		} catch (JDOMException e) {
			log.warning("error de JDOM");
			e.printStackTrace();
		} catch (IOException e) {
			log.warning("error del String Reader...");
			e.printStackTrace();
		}

	}

	/**
	 * Guarda el l'Arbre de les metadades en un fitxer XML i el desa a laCOlla
	 * 
	 */
	@SuppressWarnings("deprecation")
	public void DesaTree() {

		String arbrexml = null;
		StringWriter sw = new StringWriter();

		Element rootdir = tree.CrearXML();
		XMLOutputter outputter = new XMLOutputter("", true);

		try {
			outputter.output(new Document(rootdir), sw);
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		arbrexml = sw.toString();
		// p("Metadades: " + arbrexml);//
		// SYSOUT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!SYSOUT!!!!!!!!!!!!!

		LaColla.core.data.app.Event evt;
		LaColla.core.data.Timestamp s = new LaColla.core.data.Timestamp();
		String memberId;
		memberId = ((ApiImpl) aserv.getASAI()).getMemberId();
		evt = new LaColla.core.data.app.Event(memberId, apiid, groupId,
				"pepito1", s, "pepito4", constant.eventApplication, arbrexml,
				"pepito2");
		try {
			// modifico el xml de les metadades en local abans de llençar el
			// event aixi, ens asegurem de que el sistema de fitxers no dona un
			// error al usuari al comprovar els canvis fets ( per exemple, al
			// fer un mkdir, dp de el mkdir fa un getatr, si el event encara no
			// ha arribat, donara un error de que el directori no s'ha pogut
			// crear quan s'ha creat sense cap problema

			((ApiImpl) aserv.getASAI()).setTree(arbrexml);
			// p("enviant event....");
			api.disseminateEvent(groupId, evt);

		} catch (RemoteException e) {

			e.printStackTrace();
		}

	}

	/**
	 * Retorna el Stat del FileSystem TODO
	 * 
	 * @return
	 */
	public FuseStatfs getStat() {

		return statfs;
	}

	/**
	 * Obté el nom d'un objecte
	 * 
	 * @param path
	 * @return
	 */
	public String ObtenirNom(String path) {
		String[] pathParts = path.split("/");

		return pathParts[pathParts.length - 1];
	}

	/**
	 * Obté el path del pare d'el objecte
	 * 
	 * @param path
	 * @return
	 */
	public String ObtenirDir(String path) {

		String[] pathParts = path.split("/");
		String pathnode;
		char[] c = path.toCharArray();
		//System.out.println("últim caràcter del path : "+path +"  " + c.length);
		if ((c[c.length-1] == '/')&&(c.length>1)) {
			pathnode = path;
		} else {

			if (pathParts.length <= 2)
				pathnode = "/";
			else
				pathnode = ""; //

			for (int i = 1; i < pathParts.length - 1; i++) {
				pathnode = pathnode + "/" + pathParts[i];
			}
		}

		return pathnode;
	}

	public void moureNode(String from, String to) {
		String pareto;
		String nomto;

		pareto = ObtenirDir(to); // nom ruta desti
		nomto = ObtenirNom(to); // nom fitxer desti

		Node nodefrom;
		Directori nodeto;

		log.info("pare to " + pareto);
		nodeto = (Directori) ObtenirNode(pareto);
		nodefrom = ObtenirNode(from); // element a moure
		// nodeto.AfegirFill(nodefrom);

		nodefrom.getPare().EliminarFillRecursiu(nodefrom.getNom());
		// Element xml = ((Directori)nodefrom).CrearXML();

		// Directori nou = new Directori(null, null);
		// nou.LlegirXML(xml);
		// p("nodeto buit?" + nodeto.Buit());
		nodeto.AfegirFill(nodefrom);
		// p("nodeto " + nodeto.getNom());

		nodeto.getPare().EliminarFillRecursiu(nodeto.getNom());
		nodeto.getPare().AfegirFill(nodeto);

		// no se per que el tree de les metadades no s'actualitza, en canvi en
		// el mkdir si... i es gairebe igual...

		DesaTree();

	}

	/*
	 * public ApiServer getAserv() { // TODO Auto-generated method stub return
	 * aserv; }
	 * 
	 * public Api getApi() { // TODO Auto-generated method stub return api; }
	 */
}
